using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Campus
{
    public class KeyboardMenu {
        #region Type Definitions
        /// <summary>
        /// The function signature that the Game must use for options selected
        /// <param name="option">Upon calling, the name of the option selected</param>
        /// </summary>
        //public delegate void optionDelegate();
        // TODO: Is there a reason this delegate has to take in a string argument?
        public delegate void optionDelegate(string option);
        #endregion

        #region Fields and their getters/setters
        SpriteFont font;

        /// <summary>
        /// The graphic the menu will use when drawn.
        /// </summary>
        private Texture2D m_texture;
        public Texture2D Texture
        {
            get { return m_texture; }
            set { m_texture = value; }
        }

        /// <summary>
        /// The collection of the options shown on the menu
        /// </summary>
        private List<KeyValuePair<string, optionDelegate>> m_options = new List<KeyValuePair<string, optionDelegate>>();
        public List<KeyValuePair<string, optionDelegate>> Options
        {
            get { return m_options; }
            set { m_options = value; }
        }

        /// <summary>
        /// The index of the active item in the Menu.
        /// </summary>
        private int m_activeItem = 0;
        public int ActiveItem
        {
            get { return m_activeItem; }
            set { m_activeItem = value; }
        }

        /// <summary>
        /// Where the Menu will be drawn (absolute coords. of upper left corner)
        /// </summary>
        private Vector2 m_location = new Vector2(0, 0);
        public Vector2 Location
        {
            get { return m_location; }
            set { m_location = value; }
        }

        /// <summary>
        /// Color to tint the text when drawn
        /// </summary>
        private Color m_textColor = Color.White;
        public Color TextColor
        {
            get { return m_textColor; }
            set { m_textColor = value; }
        }

        /// <summary>
        /// Color to tint the background when drawn
        /// </summary>
        private Color m_backgroundColor = Color.White;
        public Color BackgroundColor
        {
            get { return m_backgroundColor; }
            set { m_backgroundColor = value; }
        }

        /// <summary>
        /// The opacity with which to draw the Menu
        /// </summary>
        //public byte m_opacity
        //{
        //    get { return opacity; }
        //    set { if (value >= 0 && value <= 255) opacity = value; }
        //}
        //private byte opacity = 255;

        /// <summary>
        /// X and Y size of the Menu
        /// </summary>
        private Vector2 m_size = new Vector2(0, 0);
        public Vector2 Size
        {
            get { return m_size; }
            set { m_size = value; }
        }

        /// <summary>
        /// The InputHandler that will contain the default keys for navigating a menu.
        /// (i.e. up/down, enter, mouse functions, etc.)
        /// </summary>
        private InputHandler m_inputHandler = new InputHandler();

        /// <summary>
        /// The title of the menu
        /// </summary>
        private String m_title = "";
        public String Title
        {
            get { return m_title; }
            set { m_title = value; }
        }

        private int m_contentPadding = 10;
        public int ContentPadding
        {
            get { return m_contentPadding; }
            set { m_contentPadding = value; }
        }

        private int m_textPadding = 20;
        public int TextPadding
        {
            get { return m_textPadding; }
            set { m_textPadding = value; }
        }

        private bool m_isVisible = true;
        public bool IsVisible
        {
            get { return m_isVisible; }
            set { m_isVisible = value; }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new menu, with default input handling functions.
        /// <param name="menuTexture">The background texture of the menu.</param>
        /// <param name="spriteFont">The font to be used for title and options in the menu.</param>
        /// <param name="title">The heading displayed at the top of the menu.</param>
        /// <param name="location">The location of the menu (upper-left corner)</param>
        /// <param name="size">The size of the menu</param>
        /// </summary>
        public KeyboardMenu(Texture2D menuTexture, SpriteFont spriteFont, string title, Vector2 location, Vector2 size) {
            SetDefaultBinds();

            Texture = menuTexture;
            font = spriteFont;
            Title = title;
            Location = location;
            Size = size;
        }
        /// <summary>
        /// Creates a new menu, with default input handling functions.
        /// <param name="menuTexture">The background texture of the menu.</param>
        /// <param name="spriteFont">The font to be used for title and options in the menu.</param>
        /// <param name="title">The heading displayed at the top of the menu.</param>
        /// <param name="location">The location of the menu (upper-left corner)</param>
        /// <param name="size">The size of the menu</param>
        /// <param name="color">The background color of the menu.</param>
        /// <param name="textColor">The text color of the menu.</param>
        public KeyboardMenu(Texture2D menuTexture, SpriteFont spriteFont, string title, Vector2 location, Vector2 size, Color color, Color textColor)
        {
            SetDefaultBinds();

            m_texture = menuTexture;
            font = spriteFont;
            Title = title;
            Location = location;
            Size = size;
            BackgroundColor = color;
            TextColor = textColor;
        }

        /// <summary>
        /// Creates a new menu, with default input handling functions.
        /// <param name="menuTexture">The background texture of the menu.</param>
        /// <param name="spriteFont">The font to be used for title and options in the menu.</param>
        /// <param name="title">The heading displayed at the top of the menu.</param>
        /// <param name="location">The location of the menu (upper-left corner)</param>
        /// <param name="size">The size of the menu</param>
        /// <param name="color">The background color of the menu.</param>
        /// <param name="textColor">The text color of the menu.</param>
        /// <param name="contentPadding">The number of pixels between the edge of the menu texture and the menu content (text).</param>
        /// <param name="textPadding">The number of pixels between the draw-point of each option.</param>
        /// <param name="openKey">The keyboard key that will be used to open the menu.</param>
        public KeyboardMenu(Texture2D menuTexture, SpriteFont spriteFont, string title, Vector2 location, Vector2 size, Color color, Color textColor, int contentPadding, int textPadding, Keys openKey)
        {
            SetDefaultBinds();
            m_inputHandler.Bind(openKey, ChangeVisibility, 1);

            m_texture = menuTexture;
            font = spriteFont;
            Title = title;
            Location = location;
            Size = size;
            BackgroundColor = color;
            TextColor = textColor;
            ContentPadding = contentPadding;
            TextPadding = textPadding;
        }
        #endregion

        #region Other functions
        /// <summary>
        /// Sets the default bindings for navigating the menu.
        /// </summary>
        public void SetDefaultBinds()
        {
            m_inputHandler.Bind(Keys.Down, MoveSelection, 1);
            m_inputHandler.Bind(Keys.Up, MoveSelection, 1);
            m_inputHandler.Bind(Keys.Enter, SelectItem, 1);
        }

        /// <summary>
        /// Adds the option/action pair into the list of options on the Menu
        /// <param name="option">The name of the option to add</param>
        /// <param name="action">The action that should be taken if that option is selected</param>
        /// </summary>
        public void AddOption(string option, optionDelegate action)
        {
            KeyValuePair<string, optionDelegate> newOption = new KeyValuePair<string, optionDelegate>(option, action);

            Options.Add(newOption);
        }

        /// <summary>
        /// Inserts the option/action pair into the list of options on the Menu
        /// <param name="option">The name of the option to insert</param>
        /// <param name="action">The action that should be taken if that option is selected</param>
        /// <param name="index">The index at which to insert the option</param>
        /// </summary>
        public void InsertOption(string option, optionDelegate action, int index)
        {
            KeyValuePair<string, optionDelegate> newOption = new KeyValuePair<string, optionDelegate>(option, action);

            Options.Insert(index, newOption);
        }

        /// <summary>
        /// Removes the option with the given name from the Menu
        /// <param name="option">The name of the option to remove</param>
        /// </summary>
        public void RemoveOption(string option) {
            KeyValuePair<string, optionDelegate>[] optionsArray = Options.ToArray();

            for (int i = 0; i < optionsArray.Length; i++)
                if (optionsArray[i].Key.Equals(option))
                    Options.RemoveAt(i);
        }

        public bool ContainsOption(string option)
        {
            KeyValuePair<string, optionDelegate>[] optionsArray = Options.ToArray();

            for (int i = 0; i < optionsArray.Length; i++)
                if (optionsArray[i].Key.Equals(option))
                    return true;
            return false;
        }


        private int GetLongestOption()
        {
            KeyValuePair<string, optionDelegate>[] optionsArray = Options.ToArray();
            int max = 0;

            for (int i = 0; i < Options.Count; i++)
                max = Math.Max(max, optionsArray[i].Key.Length);

            return max;
        }

        /// <summary>
        /// Checks inputs through the inputHandler (for navigating choices, selecting choices, etc.)
        /// </summary>
        public void Update()
        {
            // This causes the inputHandler to run through its binds and invoke any delegates.
            m_inputHandler.CheckInputs();
        }

        /// <summary>
        /// Moves the selected menu item up or down depending on the key pressed.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="state"></param>
        private void MoveSelection(Keys key, KeyState state)
        {
            if (IsVisible)
            {
                if (key == Keys.Down)
                    if (ActiveItem < Options.Count - 1)
                        ActiveItem++;
                if (key == Keys.Up)
                    if (ActiveItem > 0)
                        ActiveItem--;
            }
        }

        /// <summary>
        /// This function should be bound in the input handler to whatever button should select 
        /// the active item on the Menu (i.e. Enter).  It will then be called when the button is activated.
        /// <param name="state">State of the button</param>
        /// </summary>
        private void SelectItem(Keys key, KeyState state) {
            if (IsVisible)
                Options[ActiveItem].Value.Invoke(Options[ActiveItem].Key);
        }

        /// <summary>
        /// Reverses the visibility of the menu.  Mainly for use with a bind that will show/hide the menu when
        /// pressed.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="state"></param>
        private void ChangeVisibility(Keys key, KeyState state)
        {
                IsVisible = !IsVisible;
        }

        /// <summary>
        /// Draws the Menu (highlighting the active item).
        /// </summary>
        public void Draw(SpriteBatch spriteBatch) {
            if (IsVisible)
            {
                spriteBatch.Begin();

                // If the size == 0, determine what the size of the menu ought to be to house the current number of options.
                if (Size.X == 0 || Size.Y == 0)
                {
                    Size = new Vector2(GetLongestOption() * 15 + m_contentPadding, m_contentPadding * 2 + 10 + Options.Count * m_contentPadding);
                }
                // Draw the main menu texture with determined size.
                spriteBatch.Draw(m_texture, new Rectangle((int)Location.X, (int)Location.Y, (int)Size.X, (int)Size.Y), BackgroundColor);
                // Draw the title of the menu at the top, make it a little distance further in the x than the options.
                spriteBatch.DrawString(font, Title, new Vector2(m_location.X + m_contentPadding + 10, m_location.Y + m_contentPadding / 2), TextColor);
                // Draw the options.
                if (Options.Count > 0)
                    for (int i = 0; i < Options.Count; i++)
                    {
                        Color activeColor = TextColor;

                        // Highlight the selected one.
                        if (i == ActiveItem)
                            activeColor = new Color(255, 255, 0);

                        spriteBatch.DrawString(font, Options[i].Key, new Vector2(m_location.X + m_contentPadding, m_location.Y + m_textPadding * (i + 2)), activeColor);
                    }
                // If there are no options defined, say so.
                else
                    spriteBatch.DrawString(font, "", new Vector2(m_location.X + m_contentPadding, m_location.Y + m_textPadding * 2), TextColor);

                spriteBatch.End();
            }
        }

        public void Clear()
        {
            Options.Clear();
        }
        #endregion
    }
}